"""
Copyright 2009 Texas A&M University
 
Licensed under the Apache License, Version 2.0 (the "License"); you may not use 
this file except in compliance with the License. You may obtain a copy of the 
License at

http://www.apache.org/licenses/LICENSE-2.0
  
Unless required by applicable law or agreed to in writing, software distributed 
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 
CONDITIONS OF ANY KIND, either express or implied. See the License for the 
specific language governing permissions and limitations under the License. 
"""

from datetime import datetime
from logging import getLogger

import zope.interface
from zope.interface.exceptions import DoesNotImplement

from lxml import etree

from sqlalchemy import MetaData
from sqlalchemy.schema import Column, ForeignKey, Table, ForeignKeyConstraint
from sqlalchemy.types import Integer, Unicode, DateTime, TypeDecorator, Boolean, UnicodeText
from sqlalchemy.orm import relation, join, backref, object_session
from sqlalchemy.sql.expression import asc, desc, and_

from meercat.interfaces import IResource, IInstance, IMetadata, IMutableInstance, IMutableResource
from meercat.storage.interfaces import IPersistent



log = getLogger('meercat.storage.sqlalchemy.declarative')

__all__ = ['createClasses']

def createClasses(storage):
    Base = storage.Base
    
    class Metadata(Base):
        zope.interface.implements(IMetadata,IPersistent)
        __tablename__ = 'metadata'
        __table_args__ = (ForeignKeyConstraint(['instance_id','instance_source'],
                                               ['instance.id','instance.source'],
                                               onupdate='CASCADE',
                                               ondelete='RESTRICT'),{})
        
        id = Column('id', Integer, primary_key=True)
        instance_id = Column('instance_id', Unicode(64), index=True)
        instance_source = Column('instance_source', Unicode(128), index=True)
        source = Column('source', Unicode(128), index=True)
        format = Column('format', Unicode(32))
        value = Column('value', UnicodeText)
        
        def __init__(self,format=None,source=None,value=None, modified=None, instance=None):
            super(Metadata,self).__init__()
            if source is not None:
                self.source = source
            
            if format is not None:
                self.format = format
            
            if value is not None:
                self.value = value
            
            if modified is not None:
                self.modified = modified
            
            if instance is not None:
                self.instance = storage.adaptInstance(instance)
        
        def __eq__(self, other):
            if not IMetadata.providedBy(other):
                return False
            
            return (other.getSource() == self.getSource() and
                    other.getFormat() == self.getFormat() and
                    other.getValue() == self.getValue())
            
        def __hash__(self):
            return hash((self.getSource(),self.getFormat(),self.getValue(),))    
        
        def __unicode__(self):
            return u'Metadata(source=%s, format=%s, value=%s)' % (str(self._source),
                                                                  str(self._format),
                                                                  str(self._value))
        
        def getFormat(self):
            return self.format
      
        def getSource(self):
            return self.source
      
        def getValue(self):
            return self.value
        
        def getInstance(self):
            return self.instance
        
        def setInstance(self, instance):
            self.instance = instance
        
        def save(self):
            self.instance.modified = datetime.now()
            obj_session = object_session(self)
            obj_session.add(self)
    
    class Instance(Base):
        zope.interface.implements(IMutableInstance,IPersistent)
        __tablename__ = 'instance'
        
        id = Column('id', Unicode(64), primary_key=True)
        source = Column('source', Unicode(128), primary_key=True)
        resource_id = Column('resource_id', Integer, ForeignKey('resource.id'),
                             index=True)
        active = Column('active', Boolean, default=True, index=True)
        modified = Column('modified',DateTime, default=datetime.now, index=True)
        _metadata = relation(Metadata, lazy=True, cascade='all, delete-orphan',
                             backref=backref('instance', lazy=False, 
                                             cascade='all'),
                             primaryjoin=and_(id==Metadata.instance_id,
                                              source==Metadata.instance_source))
        
        def __init__(self,identifier=None,source=None,active=True,metadata=[],
                     modified=None,resource=None):
            super(Instance,self).__init__()
            if identifier is not None:
                self.id = identifier
            
            if source is not None:
                self.source = source
            
            if active:
                self.activate()
            else:
                self.deactivate()
            
            if modified:
                self.modified = modified
            
            if resource is not None:
                self.resource = storage.adaptResource(resource)
            
            self.append(metadata)
        
        def __len__(self):
            return len(self._metadata)
        
        def __iter__(self):
            return self._metadata.__iter__()
        
        def __hash__(self):
            return hash((self.getSource(),self.getIdentifier(),))
        
        def __eq__(self,other):
            if not IInstance.providedBy(other):
                return False
            
            return (other.getSource() == self.getSource() and
                    other.getIdentifier() == self.getIdentifier()) 
        
        def isActive(self):
            return self.active
          
        def getSource(self):
            return self.source
      
        def getIdentifier(self):
            return self.id
      
        def getMetadata(self):
            return self._metadata
            
        
        def getLastModified(self):
            return self.modified
        
        def deactivate(self):
            self.active = False
        
        def activate(self):
            self.active = True
        
        def getResource(self):
            return self.resource
        
        def setResource(self, resource):
            self.resource = storage.adaptResource(resource)
        
        def _append(self,metadata):
            """Adapt the incoming metadata to the local classes"""
            adapted = storage.adaptMetadata(metadata, self)
            
            if adapted not in self.metadata:
                self._metadata.append(adapted)
        
        def append(self,metadata):
            log.debug('%s.append(%s)' % (str(self),str(metadata)))
            if IMetadata.providedBy(metadata):
                self._append(metadata)
            elif hasattr(metadata,'__iter__'):
                for m in metadata:
                    self._append(m)
            else:
                raise Exception('metadata must implement the IMetadata '
                                'interface or be an iterable container of '
                                'objects that implement the IMetadata inteface')
        
        def remove(self,metadata):
            self._metadata.remove(storage.adaptMetadata(metadata, self))
        
        def removeFromSource(self,source=None):
            if source is None:
                source = self.getSource()
            
            for metadata in filter(lambda x: x.getSource() == source,
                                   self.metadata):
                self._metadata.remove(metadata)
        
        def save(self):
            self.modified = datetime.now()
            obj_session = object_session(self)
            obj_session.add(self)
    
    class Resource(Base):
        zope.interface.implements(IMutableResource,IPersistent)
        __tablename__ = 'resource'
        
        id = Column('id', Integer, primary_key=True)
        instances = relation(Instance, lazy=False, cascade='all, delete-orphan',
                             backref=backref('resource', lazy=False, 
                                             cascade='all'))
        
        def __init__(self,identifier=None,instances=[]):
            super(Resource,self).__init__()
            if identifier is not None:
                self.id = identifier
            
            for instance in instances:
                self._append(instance)
        
        def __len__(self):
            return len(self.instances)
        
        def __iter__(self):
            return self.instances.__iter__()
        
        def __eq__(self,other):
            return (IResource.providedBy(other) and 
                    other.getIdentifier() == self.getIdentifier())
        
        def getIdentifier(self):
            return self.id
        
        def getMetadata(self):
            metadata = []
            
            for instance in self.instances:
                metadata.extend(instance.getMetadata())
            
            return metadata
        
        def getLastModified(self):
            return max(map(lambda x: x.getLastModified(), self.instances))
        
        def isActive(self):
            for instance in self.instances:
                if instance.isActive():
                    return True
            
            return False
        
        def deactivate(self):
            for instances in self.instances:
                instance.deactivate()
        
        def _append(self,instance):
            """Adapt the incoming instance to the local classes"""
            adapted = storage.adaptInstance(instance, self)
            
            if adapted not in self.instances:
                self.instances.append(adapted)
        
        def append(self, instance):
            log.debug('%s.append(%s)' % (str(self),str(instance)))
            if IInstance.providedBy(instance):
                self._append(instance)
            elif hasattr(instance,'__iter__'):
                for inst in instance:
                    self._append(inst)
            else:
                raise Exception('instance must implement the IInstance interface '
                                'or be an iterable container of objects '
                                'implementing the IInstance interface')
        
        def remove(self, instance):
            self.instances.remove(storage.adaptInstance(instance, self))
        
        def save(self):
            obj_session = object_session(self)
            obj_session.add(self)
    
    
    return {'Metadata':Metadata,
            'Instance':Instance,
            'Resource':Resource}